#include "fs.h"
#include "inode.h"
#include "super.h"
#include "buf.h"
#include <stdio.h>

/*===========================================================================*
 *				fs_defrag				     *
 *===========================================================================*/
PUBLIC int fs_defrag (void) {
  struct inode * ino;               /* Inode */
  dev_t dev;                        /* device # */
  struct super_block * sp;          /* The super block */
  int scale, blks_per_zone, full_or_part,
      n,                            /* number of zones the file takes */
      i;                            /* iterator */
  zone_t oldz, newz;                /* Old / new zone numbers */
  block_t old_blk, new_blk;         /* For copying blocks */
  off_t offset;                     /* Used by read_map() */
  size_t blksz, zonesz;             /* Block / Zone size */
  struct buf * buffer;

  /* Get Inode */
  dev = fs_m_in.REQ_DEV;
  ino = find_inode(dev, fs_m_in.REQ_INODE_NR);
  if(!ino) {
      fs_m_out.RES_DEFRAG = ENOENT;
      printf("fs_defrag failed - inode not found\n");
      return(OK);
  }

  /* Is the file empty? */
  if(ino->i_size == 0) {
      /* Yes! Piece of cake (the cake is a lie btw): */
      fs_m_out.RES_DEFRAG = 0;
      return(OK);
  }

  /* Get super block */
  sp = get_super(dev);

  /* Get incrementer */
  scale = sp->s_log_zone_size;         /* for block-zone conversion */
  zonesz = sp->s_block_size << scale;  /* Size of a zone */
  blks_per_zone = 1 << scale;          /* number of blocks per zone */
  blksz  = zonesz / blks_per_zone;     /* Size of a block (incrementer) */

  /* Allocate new zones: */
  n = (ino->i_size - 1) / zonesz + 1;
  newz = alloc_nzone(dev, n);
  if(newz == NO_ZONE) {
      fs_m_out.RES_DEFRAG = err_code;
      return(OK);
  }

  /* Get first block allocated by alloc_nzone() */
  new_blk = newz << scale;

  /* Copy the blocks 1 by 1 : */
  offset=0;
  while(offset < ino->i_size) {
      old_blk = read_map(ino, offset);    /* Next block in the file */

      buffer = get_block(dev, old_blk, !NO_READ); 
      buffer->b_blocknr = new_blk;
      
      /* Increment */
      new_blk += 1;
      offset  += blksz;

      /* Is this a full or partial block? */
      if(offset <= ino->i_size)  full_or_part=FULL_DATA_BLOCK;
      else                       full_or_part=PARTIAL_DATA_BLOCK;
      put_block(buffer, full_or_part); 
  }
  
  /* Update the inode zones : */
  offset=0;
  while(offset < ino->i_size) {
      oldz = read_map(ino, offset);
      write_map(ino, offset, newz++, !WMAP_FREE);
      /* Free in last to prevent data loss in case of unexpected shutdown */
      free_zone(dev, oldz);

      offset  += zonesz;
  }

  fs_m_out.RES_DEFRAG = 0;
  return(OK);
}
